package io.adrainty.bolee.file.handler.cos;

import com.qcloud.cos.COSClient;
import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.auth.BasicCOSCredentials;
import com.qcloud.cos.auth.COSCredentials;
import com.qcloud.cos.exception.CosServiceException;
import com.qcloud.cos.exception.MultiObjectDeleteException;
import com.qcloud.cos.http.HttpProtocol;
import com.qcloud.cos.model.*;
import com.qcloud.cos.region.Region;
import io.adrainty.bolee.file.handler.AbsFileStorageHandler;
import io.adrainty.bolee.file.models.entity.FileEntity;
import io.adrainty.bolee.file.models.valobj.FileStorageEnum;
import io.adrainty.bolee.framework.commons.basic.ResponseCode;
import io.adrainty.bolee.framework.commons.exception.AppException;
import io.adrainty.bolee.framework.commons.utils.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

import java.io.InputStream;
import java.net.URL;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

/**
 * <p>CosFileStorageHandler</p>
 *
 * @author AdRainty
 * @version V1.0.0
 * @description CosFileStorageHandler
 * @since 2025/8/18 11:51:59
 */

@Slf4j
@Component
@SuppressWarnings("unused")
@EnableConfigurationProperties(CosConfigProperties.class)
@ConditionalOnProperty(prefix = "itheima.framework.storage.cos", name = "enable", havingValue = "true")
public class CosFileStorageHandler extends AbsFileStorageHandler {

    private final COSClient cosClient;

    private final CosConfigProperties properties;

    public CosFileStorageHandler(CosConfigProperties properties) {
        this.properties = properties;
        COSCredentials credentials = new BasicCOSCredentials(properties.getSecretId(), properties.getSecretKey());
        Region region = new Region(properties.getRegion());
        ClientConfig clientConfig = new ClientConfig(region);
        clientConfig.setHttpProtocol(HttpProtocol.https);
        this.cosClient = new COSClient(credentials, clientConfig);
    }

    @Override
    public String uploadFile(String suffix, String filename, String bucketName, boolean autoCatalog, InputStream inputStream) {
        String key = autoCatalog ? buildDirPath(filename) : filename;
        PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, key, inputStream, new ObjectMetadata());
        try {
            cosClient.putObject(putObjectRequest);
        } catch (Exception e) {
            throw handleCosException(e);
        }
        return key;
    }

    @Override
    public FileEntity initiateMultipartUpload(String suffix, String filename, String bucketName, boolean autoCatalog) {
        InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, filename);
        ObjectMetadata metadata = new ObjectMetadata();
        // 指定该Object的网页缓存行为。
        metadata.setCacheControl("no-cache");
        // 指定该Object被下载时的名称。
        metadata.setContentDisposition("inline;filename=" + filename);
        request.setObjectMetadata(metadata);

        InitiateMultipartUploadResult initResult = cosClient.initiateMultipartUpload(request);
        return FileEntity.builder().bucketName(bucketName)
                .pathUrl(filename).fileName(filename)
                .uploadId(initResult.getUploadId())
                .build();
    }

    @Override
    public String uploadPart(String upLoadId, String filename, int partNumber, long partSize, String bucketName, InputStream inputStream) {
        UploadPartRequest uploadPartRequest = new UploadPartRequest();
        uploadPartRequest.setBucketName(bucketName);
        uploadPartRequest.setKey(filename);
        uploadPartRequest.setUploadId(upLoadId);
        uploadPartRequest.setInputStream(inputStream);
        uploadPartRequest.setPartSize(partSize);
        uploadPartRequest.setPartNumber(partNumber);

        try {
            UploadPartResult uploadPartResult = cosClient.uploadPart(uploadPartRequest);
            log.info("{}文件第{}片上传成功", upLoadId, uploadPartRequest.getPartNumber());
            PartETag partETag = uploadPartResult.getPartETag();
            return JsonUtil.toJson(partETag);
        } catch (Exception e) {
            throw handleCosException(e);
        }
    }

    @Override
    public String completeMultipartUpload(String upLoadId, List<String> partETags, String filename, String bucketName) {
        StopWatch st = new StopWatch();
        st.start();
        List<PartETag> partETagList = partETags.stream()
                .map(part -> JsonUtil.fromJson(part, PartETag.class)).toList();
        CompleteMultipartUploadRequest completeMultipartUploadRequest =
                new CompleteMultipartUploadRequest(bucketName, filename, upLoadId, partETagList);
        log.info("{}文件上传完成,开始合并, partList:{}", upLoadId, partETags);
        try {
            CompleteMultipartUploadResult completeMultipartUploadResult =
                    cosClient.completeMultipartUpload(completeMultipartUploadRequest);
            st.stop();
            log.info("{}文件上传合并成功, 耗时:{}", upLoadId, st.getTotalTimeMillis());
            return completeMultipartUploadResult.getETag();
        } catch (Exception e) {
            st.stop();
            log.error("{}文件上传合并失败, 耗时:{}", upLoadId, st.getTotalTimeMillis());
            throw handleCosException(e);
        }
    }

    @Override
    public InputStream downloadFile(String bucketName, String pathUrl) {
        GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, pathUrl);
        try {
            return cosClient.getObject(getObjectRequest).getObjectContent();
        } catch (Exception e) {
            throw handleCosException(e);
        }
    }

    @Override
    public void delete(String bucketName, String pathUrl) {
        try {
            cosClient.deleteObject(bucketName, pathUrl);
        } catch (Exception e) {
            throw handleCosException(e);
        }
    }

    @Override
    public void deleteBatch(String bucketName, List<String> pathUrls) {
        DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(bucketName);
        List<DeleteObjectsRequest.KeyVersion> keyVersions = pathUrls.stream().map(DeleteObjectsRequest.KeyVersion::new).toList();
        deleteObjectsRequest.setKeys(keyVersions);

        try {
            cosClient.deleteObjects(deleteObjectsRequest);
        } catch (MultiObjectDeleteException mde) {
            // 如果部分删除成功部分失败, 返回 MultiObjectDeleteException
            List<DeleteObjectsResult.DeletedObject> deleteObjects = mde.getDeletedObjects();
            List<MultiObjectDeleteException.DeleteError> deleteErrors = mde.getErrors();
            String errorMsg = deleteErrors.stream().map(item -> {
                String key = item.getKey();
                String message = item.getMessage();
                String code = item.getCode();
                return String.format("key: %s, message: %s, code: %s", key, message, code);
            }).collect(Collectors.joining("\n"));
            log.error("批量删除文件失败, 成功数: {} 失败数: {}, 失败消息: {}", deleteObjects.size(), deleteErrors.size(), errorMsg);
        } catch (Exception e) {
            throw handleCosException(e);
        }
    }

    @Override
    public String getReadablePath(String bucketName, String pathUrl) {
        Date expiration = new Date(new Date().getTime() + commonProperties.getTempFileReadTimeout());
        try {
            URL url = cosClient.generatePresignedUrl(bucketName, pathUrl, expiration);
            return url.toString();
        } catch (Exception e) {
            throw handleCosException(e);
        }
    }

    @Override
    public FileStorageEnum supportStorage() {
        return FileStorageEnum.TENCENT_COS;
    }

    private AppException handleCosException(Exception e) {
        if (e instanceof CosServiceException cse) {
            // 用于指交互正常完成，但是操作失败的场景
            log.error("ServiceException: {} [{}]({}){}", cse.getErrorType(), cse.getRequestId(),
                    cse.getErrorCode(), cse.getErrorMessage());
        } else {
            log.error("Unknown Exception: ", e);
        }
        return new AppException(ResponseCode.FILE_UPLOAD_ERROR);
    }

}
